/**
 *  Copyright (C) 2021 Anthony Chomienne
 *  This program is free software: you can redistribute it and/or modify it under the terms of the
 *  GNU Affero General Public License as published by the Free Software Foundation, version 3.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License along with this program.
 *  If not, see <https://www.gnu.org/licenses/>
 */

package fr.mobdev.peertubelive.manager

import android.content.Context
import android.os.Bundle
import fr.mobdev.peertubelive.R
import fr.mobdev.peertubelive.objects.*
import org.json.JSONObject
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

object InstanceManager {
    private val oauthManager : OAuthManager = OAuthManager()

    private const val BASE_API_ENDPOINT: String = "/api/v1"
    private const val REGISTER_CLIENT_ENDPOINT: String = "/oauth-clients/local"
    private const val GET_USER_CLIENT_ENDPOINT: String = "/users/token"
    private const val GET_USER_INFO_ENDPOINT: String = "/users/me"
    private const val CREATE_LIVE_ENDPOINT: String = "/videos/live"
    private const val GET_CATEGORY_ENDPOINT: String = "/videos/categories"
    private const val GET_PRIVACY_ENDPOINT: String = "/videos/privacies"
    private const val GET_LICENCE_ENDPOINT: String = "/videos/licences"
    private const val GET_LANGUAGES_ENDPOINT: String = "/videos/languages"
    private const val GET_CONFIG_ENDPOINT: String = "/config"
    private const val GET_VIDEOS: String = "/users/me/videos"

    internal const val EXTRA_DATA: String = "EXTRA_DATA"
    private const val CONTENT_TYPE: String = "CONTENT_TYPE"
    private const val CONTENT_DATA: String = "CONTENT_DATA"

    private const val VIDEO_CHANNEL: String = "videoChannels"
    private const val CHANNEL_ID: String = "id"
    private const val CHANNEL_NAME: String = "displayName"
    private const val VIDEO: String = "video"
    private const val UUID: String = "uuid"
    private const val RTMP_URL: String = "rtmpUrl"
    private const val STREAM_KEY: String = "streamKey"
    private const val CONFIG_LIVE: String = "live"
    private const val CONFIG_LIVE_ENABLED: String = "enabled"
    private const val CONFIG_LIVE_SAVE_REPLAY: String = "allowReplay"

    fun registerAccount(context: Context, url: String, username: String, password: String, listener: InstanceListener) {
        val registerUrl = url + BASE_API_ENDPOINT+ REGISTER_CLIENT_ENDPOINT
        val internalListener: InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val oauthData: OAuthData? = args?.getParcelable(EXTRA_DATA)
                oauthData?.baseUrl = url
                if (oauthData != null)
                    getUserToken(context, url, username, password,oauthData, listener)
                else
                    listener.onError(context.getString(R.string.unknwon_error))
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }

        }
        oauthManager.register(context,registerUrl,internalListener)
    }

    fun getUserToken(context: Context, url: String, username: String, password: String, oauthData: OAuthData, listener: InstanceListener) {
        val userAccess = url + BASE_API_ENDPOINT + GET_USER_CLIENT_ENDPOINT
        oauthManager.getUserToken(context, userAccess, username, password, oauthData, listener)
    }

    private fun refreshToken(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
        val registerUrl = url + BASE_API_ENDPOINT+ GET_USER_CLIENT_ENDPOINT
        oauthManager.refreshToken(context, registerUrl, oauthData, listener)
    }

    fun createLive(context: Context, url: String, oauthData: OAuthData, streamSettings: StreamSettings, listener: InstanceListener) {
        if(oauthData.expires < Calendar.getInstance().timeInMillis) {
            refreshToken(context,url,oauthData,object: InstanceListener {
                override fun onSuccess(args: Bundle?) {
                    val oauth: OAuthData? = args?.getParcelable(InstanceManager.EXTRA_DATA)
                    if (oauth != null) {
                        DatabaseManager.updateCredentials(context,oauth)
                        listener.onUpdateOAuthData(oauth)
                        createLiveImpl(context, url,oauth,streamSettings,listener)
                    }
                }

                override fun onError(error: String?) {
                    listener.onError(error)
                }

                override fun onUpdateOAuthData(oauthData: OAuthData) {
                    listener.onUpdateOAuthData(oauthData)
                }

            })
        } else {
            createLiveImpl(context, url, oauthData,streamSettings,listener)
        }
    }

    private fun getStreamKey(context: Context, url: String, oauthData: OAuthData, liveId: String, listener: InstanceListener) {
        val liveInfo = "$url$BASE_API_ENDPOINT$CREATE_LIVE_ENDPOINT/$liveId"
        val internalListener = object: InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }
                val streamData = extractStreamData(response)
                if (streamData == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                val results = Bundle()
                results.putParcelable(EXTRA_DATA,streamData)
                listener.onSuccess(results)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }

        }
        oauthManager.get(context,liveInfo,oauthData,internalListener)
    }

    private fun createLiveImpl(context: Context, url: String, oauthData: OAuthData, streamSettings: StreamSettings, listener: InstanceListener) {
        // val comments: Boolean, val download: Boolean, val nsfw: Boolean, val saveReplay: Boolean
        val createLiveUrl = url + BASE_API_ENDPOINT + CREATE_LIVE_ENDPOINT
        val data = Bundle()
        val boundary = "45fcc22"
        var formData: String = prepareFormData(boundary,"channelId",streamSettings.channel.toString(),false)
        formData += prepareFormData(boundary,"name",streamSettings.title,false)
        formData += prepareFormData(boundary,"privacy",streamSettings.privacy.toString(),false)
        if(streamSettings.category != null)
            formData += prepareFormData(boundary,"category",streamSettings.category.toString(),false)
        if(streamSettings.language != null)
            formData += prepareFormData(boundary,"language",streamSettings.language.toString(),false)
        if(streamSettings.description != null)
            formData += prepareFormData(boundary,"description",streamSettings.description.toString(),false)
        if(streamSettings.licence != null)
            formData += prepareFormData(boundary,"licence",streamSettings.licence.toString(),false)
        formData += prepareFormData(boundary,"commentsEnabled",streamSettings.comments.toString(),false)
        formData += prepareFormData(boundary,"nsfw",streamSettings.nsfw.toString(),false)
        formData += prepareFormData(boundary,"downloadEnabled",streamSettings.download.toString(),streamSettings.saveReplay == null)
        if (streamSettings.saveReplay != null)
            formData += prepareFormData(boundary,"saveReplay",streamSettings.saveReplay.toString(),true)
        data.putString(CONTENT_TYPE,"multipart/form-data; boundary=$boundary")
        data.putString(CONTENT_DATA,formData)
        val internalListener = object: InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }

                val liveId = extractLiveId(response)
                if (liveId == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                getStreamKey(context,url,oauthData,liveId,listener)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }

        }
        oauthManager.post(context,createLiveUrl,oauthData,data,internalListener)
    }

    fun getUserChannelList(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
        if(oauthData.expires < Calendar.getInstance().timeInMillis) {
            refreshToken(context,url,oauthData,object: InstanceListener {
                override fun onSuccess(args: Bundle?) {
                    val oauth: OAuthData? = args?.getParcelable(EXTRA_DATA)
                    if (oauth != null) {
                        listener.onUpdateOAuthData(oauth)
                        DatabaseManager.updateCredentials(context,oauth)
                        getUserChannelListImpl(context, url,oauth,listener)
                    }
                }

                override fun onError(error: String?) {
                    listener.onError(error)
                }

                override fun onUpdateOAuthData(oauthData: OAuthData) {
                    listener.onUpdateOAuthData(oauthData)
                }

            })
        } else {
            getUserChannelListImpl(context, url, oauthData, listener)
        }
    }

    private fun getUserChannelListImpl(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
            val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_USER_INFO_ENDPOINT
            val internalListener : InstanceListener = object : InstanceListener {
                override fun onSuccess(args: Bundle?) {
                    val response = args?.getString(EXTRA_DATA, null)
                    if (response == null) {
                        listener.onError(context.getString(R.string.unknwon_error))
                        return
                    }

                    val channelList = extractChannelData(response)
                    if (channelList == null) {
                        listener.onError(context.getString(R.string.json_error))
                        return
                    }
                    args.putParcelableArrayList(EXTRA_DATA, channelList)
                    listener.onSuccess(args)
                }

                override fun onError(error: String?) {
                    listener.onError(error)
                }

                override fun onUpdateOAuthData(oauthData: OAuthData) {
                    listener.onUpdateOAuthData(oauthData)
                }
            }

            oauthManager.get(context,userInfoUrl,oauthData,internalListener)
    }

    fun getCategoryList(context: Context, url: String, listener: InstanceListener) {
        val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_CATEGORY_ENDPOINT
        val internalListener : InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }

                val categoryList = extractMapData<Int>(response,0)
                if (categoryList == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                categoryList[""]=0
                args.putSerializable(EXTRA_DATA, categoryList)
                listener.onSuccess(args)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }
        }

        oauthManager.get(context,userInfoUrl,null,internalListener)
    }


    fun getPrivacyList(context: Context, url: String, listener: InstanceListener) {
        val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_PRIVACY_ENDPOINT
        val internalListener : InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }

                val privacyList = extractMapData<Int>(response,0)
                if (privacyList == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                args.putSerializable(EXTRA_DATA, privacyList)
                listener.onSuccess(args)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }
        }

        oauthManager.get(context,userInfoUrl,null,internalListener)
    }

    fun getLicencesList(context: Context, url: String, listener: InstanceListener) {
        val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_LICENCE_ENDPOINT
        val internalListener : InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }

                val licencesList = extractMapData<Int>(response,0)
                if (licencesList == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                licencesList[""]=0
                args.putSerializable(EXTRA_DATA, licencesList)
                listener.onSuccess(args)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }
        }

        oauthManager.get(context,userInfoUrl,null,internalListener)
    }

    fun getLanguageList(context: Context, url: String, listener: InstanceListener) {
        val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_LANGUAGES_ENDPOINT
        val internalListener : InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }

                val languageList = extractMapData<String>(response,"")
                if (languageList == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                languageList[""]=""
                args.putSerializable(EXTRA_DATA, languageList)
                listener.onSuccess(args)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }
        }

        oauthManager.get(context,userInfoUrl,null,internalListener)
    }

    fun getLiveConfig(context: Context, url: String, listener: InstanceListener) {
        val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_CONFIG_ENDPOINT
        val internalListener : InstanceListener = object : InstanceListener {
            override fun onSuccess(args: Bundle?) {
                val response = args?.getString(EXTRA_DATA, null)
                if (response == null) {
                    listener.onError(context.getString(R.string.unknwon_error))
                    return
                }
                val configData = extractConfigData(response)
                if (configData == null) {
                    listener.onError(context.getString(R.string.json_error))
                    return
                }
                args.putParcelable(EXTRA_DATA, configData)
                listener.onSuccess(args)
            }

            override fun onError(error: String?) {
                listener.onError(error)
            }

            override fun onUpdateOAuthData(oauthData: OAuthData) {
                listener.onUpdateOAuthData(oauthData)
            }
        }

        oauthManager.get(context,userInfoUrl,null,internalListener)
    }

    private fun extractChannelData(response: String): ArrayList<ChannelData>? {
        try {
            val json = JSONObject(response)

            if (json.has(VIDEO_CHANNEL)) {
                val channelList: ArrayList<ChannelData> = ArrayList()
                val channels = json.getJSONArray(VIDEO_CHANNEL)
                for (i: Int in 0 until channels.length()) {
                    val channel = channels.getJSONObject(i)
                    if (channel.has(CHANNEL_NAME) && channel.has(CHANNEL_ID)) {
                        val name = channel.getString(CHANNEL_NAME)
                        val id = channel.getLong(CHANNEL_ID)
                        val channelData = ChannelData(id, name)
                        channelList.add(channelData)
                    } else {
                        return null
                    }
                }
                return channelList
            } else {
                return null
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    private fun extractLiveId(response: String): String? {
        try {
            val json = JSONObject(response)

            return if(json.has(VIDEO)) {
                val video = json.getJSONObject(VIDEO)
                if (video.has(UUID)) {
                    video.getString(UUID)
                } else {
                    null
                }
            } else {
                null
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }


    private fun extractStreamData(response: String): StreamData? {
        try {
            val json = JSONObject(response)

            return if (json.has(RTMP_URL) && json.has(STREAM_KEY)) {
                val rtmp = json.getString(RTMP_URL)
                val key = json.getString(STREAM_KEY)

                StreamData(rtmp,key,null)

            } else {
                null
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    private fun extractConfigData(response: String): ConfigData? {
        try {
            val json = JSONObject(response)

            return if (json.has(CONFIG_LIVE)) {
                val live = json.getJSONObject(CONFIG_LIVE)
                return if(live.has(CONFIG_LIVE_ENABLED) && live.has(CONFIG_LIVE_SAVE_REPLAY)) {
                    val liveEnabled = live.getBoolean(CONFIG_LIVE_ENABLED)
                    val saveReplayEnabled = live.getBoolean(CONFIG_LIVE_SAVE_REPLAY)
                    ConfigData(liveEnabled,saveReplayEnabled)
                } else {
                    null
                }
            } else {
                null
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    private fun <T> extractMapData(response: String, type: T): HashMap<String,T>? {
        return try {
            val json = JSONObject(response)
            val map = HashMap<String,T>()
            for(key in json.keys()) {
                if (type is Int)
                    map[json.getString(key)] = key.toInt() as T
                else if (type is String)
                    map[json.getString(key)] = key as T
            }
            map
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }


    private fun prepareFormData(boundary: String, propertyName: String, property: String, lastData: Boolean): String {
        val crlf = "\r\n"
        var formData = "--$boundary$crlf"
        formData+="Content-Disposition: form-data; name=\"$propertyName\"$crlf$crlf"
        formData+="$property$crlf"

        if(lastData) {
            formData += "--$boundary--$crlf"
        }

        return formData
    }


    interface InstanceListener{
        fun onSuccess(args: Bundle?)
        fun onError(error: String?)
        fun onUpdateOAuthData(oauthData: OAuthData)
    }
}